﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Chromium;
using Chromium.Remote;
using Chromium.Remote.Event;
using Chromium.WebBrowser;
using Fasterflect;
using Kugar.Core.BaseStruct;
using Kugar.Core.Exceptions;
using Kugar.Core.ExtMethod;
using Kugar.UI.RazorUI.Exceptions;
using Kugar.UI.RazorUI.Helpers;
using Kugar.UI.RazorUI.RazorEngine;
using Microsoft.AspNetCore.Razor.Language;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using MethodInvoker = System.Windows.Forms.MethodInvoker;

namespace Kugar.UI.RazorUI
{
    /// <summary>
    /// 针对一个页面的处理类,一个处理类对应一个页面,当页面发生切换,则对应的处理类也被清理
    /// </summary>
    public abstract class CefRazorFormBase: IRazorUIResourceHandler
    {
        private string _currentFormViewPath = "";
        private JSObject _currentEmbedObject = default; //嵌入的对象
        private bool _isClosed = false;
        private Dictionary<string, HtmlElementProxy> _cacheElements=new Dictionary<string, HtmlElementProxy>(StringComparer.CurrentCultureIgnoreCase);

        protected CefRazorFormBase(string url)
        {
            _currentFormViewPath = url;//(this.GetType().GetCustomAttribute(typeof(RazorUrlAttribute)) as RazorUrlAttribute)?.Url;
        }

        public BrowserCore Browser { get; internal set; }

        private CfxFrame _currentFrame = null;
        public CfxFrame CurrentFrame
        {
            get { return _currentFrame; }
            internal set
            {
                if (_currentFrame!=value)
                {
                    _currentFrame = value;
                    
                    if (value?.IsMain==true)
                    {
                        SourceForm.MainFrameProxyCallExecute=JsProxyCall_Execute;
                        
                    }
                }
            }
        }

        public RazorUIForm SourceForm { get; internal set; }

        public HttpRequest RequestContext { get; set; }

        public JSObject EmbedJsObject => _currentEmbedObject;

        public abstract IResult Execute();
        
        /// <summary>
        /// 开始加载页面时,触发该事件
        /// </summary>
        public event EventHandler FormLoading;

        /// <summary>
        /// 页面加载完成后触发该事件
        /// </summary>
        public event EventHandler FormLoaded;


        /// <summary>
        /// 页面跳转完成后触发前一个页面的关闭事件
        /// </summary>
        public event EventHandler FormClosed;


        /// <summary>
        /// 返回指定ID的html dom元素代理类
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        protected async Task<HtmlElementProxy> FindElementByIDAsync(string id)
        {
            try
            {
                if (_cacheElements.TryGetValue(id,out var el))
                {
                    return el;
                }
                else
                {
                    var tmp= new HtmlElementProxy(id, Browser);

                    _cacheElements.Add(id,tmp);

                    return tmp;
                }
                //CurrentFrame.V8Context.Enter();

                //if (CurrentFrame.V8Context.Eval($"_createElementProxyByID('{id}')",
                //    "http://res.razorui.local/Resources/razorui.js", 0, out var retval, out var exception))
                //{
                //    return new JsonObjectProxy(id, Browser);
                //}
                //if (await this.EvaluateJavascriptAsync<bool>($"_createElementProxyByID('{id}')"))
                //{
                    return new HtmlElementProxy(id, Browser);
                //}
                //else
                //{
                    //return null;
                //}
            }
            catch (Exception e)
            {
                var msg = e.Message;
                throw;
            }
            finally
            {
                //CurrentFrame.V8Context.Exit();
            }
        }

        ///// <summary>
        ///// 返回指定ID的html dom元素代理类
        ///// </summary>
        ///// <param name="id"></param>
        ///// <returns></returns>
        //protected JsonObjectProxy FindElementByID(string id)
        //{
        //    try
        //    {
        //        //CurrentFrame.V8Context.Enter();

        //        //if (CurrentFrame.V8Context.Eval($"_createElementProxyByID('{id}')",
        //        //    "http://res.razorui.local/Resources/razorui.js", 0, out var retval, out var exception))
        //        //{
        //        //    return new JsonObjectProxy(id, Browser);
        //        //}
        //        return FindElementByIDAsync(id).Result;
        //    }
        //    catch (Exception e)
        //    {
        //        var msg = e.Message;
        //        return null;
        //    }
        //    finally
        //    {
        //        //CurrentFrame.V8Context.Exit();
        //    }
        //}

        /// <summary>
        /// 通过变量名获取对应js对象的代理
        /// </summary>
        /// <param name="varName"></param>
        /// <returns></returns>
        protected async Task<JsonObjectProxy> GetProxyByVarNameAsync(string varName)
        {
            try
            {
                //CurrentFrame.V8Context.Enter();

                //if (CurrentFrame.V8Context.Eval($"_createElementProxyByID('{id}')",
                //    "http://res.razorui.local/Resources/razorui.js", 0, out var retval, out var exception))
                //{
                //    return new JsonObjectProxy(id, Browser);
                //}
                if (await this.EvaluateJavascriptAsync<bool>($"_createElementProxyByID({varName})"))
                {
                    return new JsonObjectProxy(varName, Browser);
                }
                else
                {
                    return null;
                }
            }
            catch (Exception e)
            {
                var msg = e.Message;
                return null;
            }
            finally
            {
                //CurrentFrame.V8Context.Exit();
            }
        }

        /// <summary>
        /// 通过变量名获取对应js对象的代理
        /// </summary>
        /// <param name="varName"></param>
        /// <returns></returns>
        protected JsonObjectProxy GetProxyByVarName(string varName)
        {
            try
            {
                return GetProxyByVarNameAsync(varName).Result;
            }
            catch (Exception e)
            {
                var msg = e.Message;
                return null;
            }
            finally
            {
                //CurrentFrame.V8Context.Exit();
            }
        }

        protected bool SetCookie(string name, string value,TimeSpan? expireDt)
        {
            var cookie=new CfxCookie();
            cookie.Name = name;
            cookie.Value = value;

            if (expireDt.HasValue)
            {
                cookie.Expires = CfxTime.FromUniversalTime(DateTime.Now.Add(expireDt.Value));
            }

            //cookie.Domain = RequestContext.Host;
            cookie.Creation = CfxTime.FromUniversalTime(DateTime.Now.ToUniversalTime());

            return  SetCookie(cookie);
        }

        protected bool SetCookie(CfxCookie cookie)
        {
            return CfxCookieManager.GetGlobalManager(null).SetCookie(RequestContext.Url, cookie,null);
        }

        protected async Task<CfxCookie[]> GetCookies(string url="")
        {
            var visitor=new CfxCookieVisitor();
            var list=new List<CfxCookie>();
            var awaiter=new AwaiterBase<List<CfxCookie>>();
            visitor.Visit += (s, args) =>
            {
                args.SetReturnValue(true);
                list.Add(args.Cookie);

                if (args.Count==list.Count)
                {
                    awaiter.SetResult(list,true);
                }
            };

            if (string.IsNullOrWhiteSpace(url))
            {
                CfxCookieManager.GetGlobalManager(null).VisitAllCookies(visitor);
            }
            else
            {
                CfxCookieManager.GetGlobalManager(null).VisitUrlCookies(url, false, visitor);
            }

            return (await awaiter).ToArrayEx();
        }

        protected bool ExecuteJavascript(string funcOrScript)
        {
            CurrentFrame.ExecuteJavaScript(funcOrScript,"",0);
            return true;
        }

        protected async Task<dynamic> EvaluateJavascriptAsync(string funcOrScript)
        {
            var awaiter = new TaskCompletionSource<dynamic>();
            
            Browser.EvaluateJavascript(funcOrScript, (v, e) =>
            {
                if (e != null)
                {
                    awaiter.SetException(new JsCodeExecuteException(e));
                }
                else
                {
                    awaiter.SetResult(CefValueHelper.ToPrimitive(v, Browser));
                }
            });

            return await awaiter.Task;
        }

        protected async Task<TValue> EvaluateJavascriptAsync<TValue>(string funcOrScript)
        {
            var t = new TaskCompletionSource<TValue>();
            
            Browser.EvaluateJavascript(funcOrScript, (v, e) =>
            {
                if (e != null)
                {
                    var msg = e.Message;

                    t.SetException(new JsCodeExecuteException(e));
                }
                else
                {
                    t.SetResult((TValue)CefValueHelper.ToPrimitive(v, Browser));
                }
            });

            return await t.Task;
        }

        protected void AddFunction(string name, Action<object,Chromium.Remote.Event.CfrV8HandlerExecuteEventArgs> executor)
        {
            _currentEmbedObject.AddFunction(name).Execute += new CfrV8HandlerExecuteEventHandler(executor);
        }

        protected void AddProperty(string name ,Action<object, CfrV8AccessorGetEventArgs> getter,Action<object, CfrV8AccessorSetEventArgs> setter=null)
        {
            var p = _currentEmbedObject.AddDynamicProperty(name);

            p.PropertyGet += new CfrV8AccessorGetEventHandler(getter);

            if (setter!=null)
            {
                p.PropertySet += new CfrV8AccessorSetEventHandler(setter);
            }
        }

        protected IResult View(object model)
        {
            return new RazorResult(_currentFormViewPath, model);
        }

        protected IResult View(string viewPath, object model)
        {
            return new RazorResult(viewPath, model);
        }


        internal void OnFormLoaded(/*CfxFrame frame*/)
        {
            Debug.WriteLine("OnFormLoaded");

            //FrameLoaded?.Invoke(this, EventArgs.Empty);

            if (!CurrentFrame.IsMain)
            {
                var go = this.SourceForm.GlobalObjectForFrame(CurrentFrame.Name);

                if (!go.ContainsKey("cSharpRazorUI"))
                {
                    //go.AddObject("cSharpRazorUI").AddFunction("proxyCall").Execute += JsProxyCall_Execute;
                }
            }
            else
            {
                FormLoaded?.Invoke(this,EventArgs.Empty);
            }
        }

        private void JsProxyCall_Execute(object sender, Chromium.Remote.Event.CfrV8HandlerExecuteEventArgs e)
        {
            if (e.Name != "proxyCall")
            {
                e.SetReturnValue(null);
                return;
            }

            if (!e.Arguments.HasData())
            {
                e.SetReturnValue(null); 
                return;
            }
            
            var args = e.Arguments.Select(x => x==null?null:CefValueHelper.ToPrimitive(x, Browser)).ToArrayEx();
            
            if (args[0].ToStringEx() == "__formloaded")
            {
                SourceForm.Invoke(new MethodInvoker(() => { FormLoaded?.Invoke(this, EventArgs.Empty); }));
               
                e.SetReturnValue(1);
            }
            else
            {
                try
                {
                    var returnValue=this.TryCallMethodWithValues(args[0].ToStringEx(), args.Skip(1).ToArrayEx());

                    //var json = CfrV8Value.CreateObject(new CfrV8Accessor());
                    //json.SetValue("return", CefValueHelper.ToCfrV8Value(returnValue), CfxV8PropertyAttribute.None);

                    e.SetReturnValue(CefValueHelper.ToCfrV8Value(returnValue));
                }
                catch (Exception exception)
                {
                    e.Exception = exception.Message;
                    e.SetReturnValue(null);
                }
                
            }
        }

        internal void OnFormLoading()
        {
            FormLoading?.Invoke(this, EventArgs.Empty);
        }

        internal void OnFormClosed()
        {
            FormClosed?.Invoke(this, EventArgs.Empty);
            _isClosed = true;
            SourceForm = null;
            CurrentFrame = null;

        }
        
    }

    public class JsonObjectProxy
    {
        private BrowserCore _browser = null;
        private string _varName = "";

        public JsonObjectProxy(string varName, BrowserCore browser)
        {
            _varName = varName;
            _browser = browser;
        }

        //public async Task<string> this[string attrName]
        //{
        //    set { _browser.ExecuteJavascript($"__getJsProxyByKey('{_remotePtr}').{attrName}='{value}'"); }
        //    get
        //    {
        //        var t = new TaskCompletionSource<string>();

        //        _browser.EvaluateJavascript($"__getJsProxyByKey('{_remotePtr}').{attrName}", (v, e) =>
        //        {
                    

        //            if (e != null)
        //            {
        //                t.SetException(new JsCodeExecuteException(e));
        //            }
        //            else
        //            {
        //                t.SetResult(CefValueHelper.ToPrimitive(v, _browser).ToStringEx());
        //            }
        //        });
                
        //        return t.Task.Result.ToStringEx();
        //    }
        //}

        public async Task SetAsync(string attrName, string value)
        {
            var t = new TaskCompletionSource<bool>();

            _browser.EvaluateJavascript($"{_varName}.{attrName}='{value}'", (v, e) =>
            {


                if (e != null)
                {
                    t.SetException(new JsCodeExecuteException(e));
                }
                else
                {
                    t.SetResult(true);
                }
            });

            await t.Task;
        }

        public async Task<string> GetAsync(string attrName)
        {
            var t = new TaskCompletionSource<string>();

            _browser.EvaluateJavascript($"{_varName}.{attrName}", (v, e) =>
            {
                if (e != null)
                {
                    t.SetException(new JsCodeExecuteException(e));
                }
                else
                {
                    t.SetResult(CefValueHelper.ToPrimitive(v, _browser).ToStringEx());
                }
            });

            return await t.Task;
        }

        public bool Execute(string funcName, params object[] args)
        {
            if (args.HasData() && !args.Any(x =>
            {
                var t = x.GetType();

                return t.IsPrimitive || t.IsValueType || t.IsSubclassOf(typeof(JToken)) || t == typeof(string);
            }))
            {
                throw new ArgumentTypeNotMatchException("参数类型错误,只允许使用元类型,或者JObject类型","");
            }

            for (int i = 0; i < args.Length; i++)
            {
                args[i] = CefValueHelper.ToCfrJsParameter(args[i]);
            }

            return _browser.ExecuteJavascript($"{_varName}).{funcName}({args.JoinToString(',')});");
        }

        public async Task<object> Eval(string funcName, object[] args, Action<object,Exception> callback)
        {
            if (!args.Any(x =>
            {
                var tmp = x.GetType();

                return tmp.IsPrimitive || tmp.IsSubclassOf(typeof(JToken)) || tmp == typeof(string);
            }))
            {
                throw new ArgumentTypeNotMatchException("参数类型错误,只允许使用元类型,或者JObject类型", "");
            }

            for (int i = 0; i < args.Length; i++)
            {
                if (args[i] is string)
                {
                    args[i] = $"'{args[i]}'";
                }
                else if (args[i] is JToken)
                {
                    args[i] = JsonConvert.SerializeObject((JToken)args[i]);
                }
            }

            //return _browser.EvaluateJavascript($"__getJsProxyByKey('{_remotePtr}').{funcName}({args.JoinToString(',')});",
            //    (v,e) => { callback(CefValueHelper.ToPrimitive(v, _browser),v.HasException?new JsCodeExecuteException(e) :null ); });

            var t = new TaskCompletionSource<object>();

            _browser.EvaluateJavascript($"{_varName}.{funcName}({args.JoinToString(',')}", (v, e) =>
            {
                if (e != null)
                {
                    t.SetException(new JsCodeExecuteException(e));
                }
                else
                {
                    t.SetResult(CefValueHelper.ToPrimitive(v, _browser));
                }
            });

            return await t.Task;
        }
    }

    public class HtmlElementProxy
    {
        private BrowserCore _browser = null;
        private string _elementID = "";
        private Lazy<Dictionary<string, EventHandler<ElementEventArgs>>> _eventHandlers=new Lazy<Dictionary<string, EventHandler<ElementEventArgs>>>(()=>new Dictionary<string, EventHandler<ElementEventArgs>>(StringComparer.CurrentCultureIgnoreCase));

        public HtmlElementProxy(string elementID, BrowserCore browser)
        {
            _elementID = elementID;
            _browser = browser;
        }

        public async Task SetAsync(string attrName, string value)
        {
            var t = new TaskCompletionSource<bool>();

            _browser.EvaluateJavascript($"document.getElementById('{_elementID}').{attrName}='{value}'", (v, e) =>
            {


                if (e != null)
                {
                    t.SetException(new JsCodeExecuteException(e));
                }
                else
                {
                    t.SetResult(true);
                }
            });

            await t.Task;
        }

        public async Task<string> GetAsync(string attrName)
        {
            var t = new TaskCompletionSource<string>();

            _browser.EvaluateJavascript($"document.getElementById('{_elementID}').{attrName}", (v, e) =>
            {
                if (e != null)
                {
                    t.SetException(new JsCodeExecuteException(e));
                }
                else
                {
                    t.SetResult(CefValueHelper.ToPrimitive(v, _browser).ToStringEx());
                }
            });

            return await t.Task;
        }

        public async Task<bool> ExecuteAsync(string funcName, params object[] args)
        {
            if (args.HasData() && !args.Any(x =>
            {
                var t = x.GetType();

                return t.IsPrimitive || t.IsValueType || t.IsSubclassOf(typeof(JToken)) || t == typeof(string);
            }))
            {
                throw new ArgumentTypeNotMatchException("参数类型错误,只允许使用元类型,或者JObject类型", "");
            }

            for (int i = 0; i < args.Length; i++)
            {
                args[i] = CefValueHelper.ToCfrJsParameter(args[i]);
            }

            return _browser.ExecuteJavascript($"document.getElementById('{_elementID}').{funcName}({args.JoinToString(',')});");
        }

        public void TriggerEvent(string eventName, params object[] args)
        {

        }

        public async Task<object> Eval(string funcName, object[] args, Action<object, Exception> callback)
        {
            if (!args.Any(x =>
            {
                var tmp = x.GetType();

                return tmp.IsPrimitive || tmp.IsSubclassOf(typeof(JToken)) || tmp == typeof(string);
            }))
            {
                throw new ArgumentTypeNotMatchException("参数类型错误,只允许使用元类型,或者JObject类型", "");
            }

            for (int i = 0; i < args.Length; i++)
            {
                if (args[i] is string)
                {
                    args[i] = $"'{args[i]}'";
                }
                else if (args[i] is JToken)
                {
                    args[i] = JsonConvert.SerializeObject((JToken)args[i]);
                }
            }

            //return _browser.EvaluateJavascript($"__getJsProxyByKey('{_remotePtr}').{funcName}({args.JoinToString(',')});",
            //    (v,e) => { callback(CefValueHelper.ToPrimitive(v, _browser),v.HasException?new JsCodeExecuteException(e) :null ); });

            var t = new TaskCompletionSource<object>();

            _browser.EvaluateJavascript($"__getJsProxyByKey('{_elementID}').{funcName}({args.JoinToString(',')}", (v, e) =>
            {
                if (e != null)
                {
                    t.SetException(new JsCodeExecuteException(e));
                }
                else
                {
                    t.SetResult(CefValueHelper.ToPrimitive(v, _browser));
                }
            });

            return await t.Task;
        }

        public void HandleEvent(string eventName, EventHandler<ElementEventArgs> handler)
        {
            if (_eventHandlers.Value.TryGetValue(eventName, out var h))
            {
                h += handler;
            }
            else
            {
                _eventHandlers.Value.Add(eventName,handler);
            }
        }
    }

    public class ElementEventArgs : EventArgs
    {

    }

    public interface IResult
    {
        void Render(TextWriter writer);
    }

    public class RazorResult : IResult
    {
        private static RazorRenderEngine _defaultEngine = null;

        private string _viewFile = "";
        private object _model = null;

        static RazorResult()
        {
            _defaultEngine = new RazorRenderEngine(Application.StartupPath);
        }

        public RazorResult(string viewFile, object model)
        {
            _viewFile = viewFile;
            _model = model;
        }

        public ViewContext ViewContext { get; internal set; }

        public void Render(TextWriter writer)
        {
            var html = _defaultEngine.Execute(_viewFile, _model, ViewContext);

            writer.Write(html);
            writer.Flush();
        }
    }
}
